package com.nn.algorithm;

import java.util.Arrays;
import java.util.Random;

import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.MatrixUtils;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;
import org.apache.commons.math3.random.RandomData;
import org.apache.commons.math3.random.RandomDataGenerator;
import org.apache.commons.math3.random.RandomDataImpl;

public class Layer {

	// input vector
	private double[] input;
	// output vector, one entry for each neuron in the next layer
	private double[] output;
	// the weight matrix of size (rows,columns)=(outputs, inputs)
	private RealMatrix weightMatrix;
	private Layer previous;
	private Layer next;
	private LayerType layerType;
	private int numberOfNeurons;

	public Layer(int neurons, LayerType layerType) {
		this.setNumberOfNeurons(neurons);
		this.setLayerType(layerType);
	}

	public double[] processLayer(double inputForFirstLayer[]) {

		if (this.getLayerType() == LayerType.INPUT) {
			this.setInput(inputForFirstLayer);
		} else {
			this.setInput(getPrevious().getOutput());
		}

		// create a row matrix with added weight
		double augmentedInput[] = new double[input.length + 1];
		augmentedInput = Arrays.copyOf(input, input.length + 1);
		augmentedInput[input.length] = 1;

		if (this.getLayerType() == LayerType.OUTPUT) {
			this.output = input;
		} else {
			int outputNeurons = this.getNext().getNumberOfNeurons();
			// calculate output for each neuron in the next layer
			for (int i = 0; i < outputNeurons; i++) {
				RealVector columnVector = this.getWeightMatrix()
						.getColumnVector(i);
				RealVector inputVector = new ArrayRealVector(augmentedInput);
				double sum = inputVector.dotProduct(columnVector);
				this.output[i] = Utils.activationFunction(sum);
			}
		}

		return input;
	}

	public void init() {
		this.input = new double[this.getNumberOfNeurons()];
		if (this.getLayerType() == LayerType.OUTPUT) {
			this.output = new double[this.getNumberOfNeurons()];
		} else {
			this.output = new double[this.getNext().getNumberOfNeurons()];
		}
		if (this.getLayerType() != LayerType.OUTPUT) {
			initializeWeightMatrix();
		}
	}

	private void initializeWeightMatrix() {

		int rows = this.getNumberOfNeurons();
		int columns = this.getNext().getNumberOfNeurons();
		System.out.println("Rows=" + rows);
		System.out.println("Columns=" + columns);
		this.weightMatrix = MatrixUtils.createRealMatrix(rows + 1, columns);

		// random matrix values
		Random rand = new Random();
		for (int i = 0; i < rows + 1; i++) {
			for (int j = 0; j < columns; j++) {
				double randomValue = -1 + (2) * rand.nextDouble();
				this.weightMatrix.setEntry(i, j, randomValue);
			}
		}
		System.out.println(this.weightMatrix);
	}

	public double[] getInput() {
		return input;
	}

	public void setInput(double[] input) {
		this.input = input;
	}

	public double[] getOutput() {
		return output;
	}

	public void setOutput(double[] output) {
		this.output = output;
	}

	public int getNumberOfNeurons() {
		return numberOfNeurons;
	}

	public void setNumberOfNeurons(int numberOfNeurons) {
		this.numberOfNeurons = numberOfNeurons;
	}

	public RealMatrix getWeightMatrix() {
		return weightMatrix;
	}

	public void setWeightMatrix(RealMatrix weightMatrix) {
		this.weightMatrix = weightMatrix;
	}

	public Layer getPrevious() {
		return previous;
	}

	public void setPrevious(Layer previous) {
		this.previous = previous;
	}

	public Layer getNext() {
		return next;
	}

	public void setNext(Layer next) {
		this.next = next;
	}

	public LayerType getLayerType() {
		return layerType;
	}

	public void setLayerType(LayerType layerType) {
		this.layerType = layerType;
	}

	public void readWeightMatrix(int indexOf) {
		
		this.weightMatrix = Persistence.readMatrix(indexOf);
	}
}
